package aiingames.simulation.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;

import org.geotools.data.FeatureSource;
import org.geotools.feature.Feature;
import org.matsim.core.gbl.MatsimRandom;
import org.matsim.core.utils.gis.ShapeFileReader;

import aiingames.Config;
import aiingames.simulation.network.Link;
import aiingames.simulation.network.Network;
import aiingames.simulation.server.physics.AgentParticle;
import aiingames.simulation.server.physics.BallParticle;
import aiingames.simulation.server.physics.Particle;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiPolygon;




public class Gui  {
	private JFrame f;
	private DrawArea area;
	
	private long lastUpdate = 0;
	private Envelope envelope;
	private double scale;

	public Gui() {
		
		this.f = new JFrame("Swing Paint Demo");
		this.f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.area = new DrawArea();
		this.f.add(area);
		this.f.setSize(250,250);
		this.f.setVisible(true);
	}
	public void setNetwork(Network network) {
		List<PolyLine> lines = new ArrayList<PolyLine>();
		for (Link link : network.getLinks()) {
			PolyLine p = new PolyLine();
			p.x = new int[2];
			p.y = new int[2];
			p.length = 2;
			p.x[0] = (int)(0.5 +this.scale *(link.getFromNode().getCoordinate().x-this.envelope.getMinX()));
			p.y[0] = (int)(0.5 +this.scale *(link.getFromNode().getCoordinate().y-this.envelope.getMinY()));
			p.x[1] = (int)(0.5 +this.scale *(link.getToNode().getCoordinate().x-this.envelope.getMinX()));
			p.y[1] = (int)(0.5 +this.scale *(link.getToNode().getCoordinate().y-this.envelope.getMinY()));
			lines.add(p);
		}
		
		
		List<Node> nodes = new ArrayList<Node>();
		int i = 0;
		for (aiingames.simulation.network.Node node : network.getNodes()) {
			
			Node n = new Node();
			int x = (int)(0.5 + this.scale * (node.getCoordinate().x - this.envelope.getMinX()));
			int y = (int)(0.5 + this.scale * (node.getCoordinate().y - this.envelope.getMinY()));
			n.x = x;
			n.y = y;
			n.coordinate = Integer.toString(i++);
			nodes.add(n);
		}
		this.area.setNodes(nodes);
		this.area.setLinks(lines);
		
	}
	
	public void setEnvironment(String env) {
		FeatureSource fs = null;
		try {
			fs = ShapeFileReader.readDataFile(env);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		try {
			this.envelope = fs.getBounds();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		resize();
		
		@SuppressWarnings("rawtypes")
		Iterator it = null;
		try {
			it = fs.getFeatures().iterator();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		List<PolyLine> lines = new ArrayList<PolyLine>();
		
		while (it.hasNext()) {
			Feature f = (Feature) it.next();
			Geometry geo = f.getDefaultGeometry();
			if (geo instanceof MultiPolygon) {
				for (int i = 0; i < geo.getNumGeometries(); i++) {
					com.vividsolutions.jts.geom.Polygon p = (com.vividsolutions.jts.geom.Polygon) geo.getGeometryN(i);
					LineString shell = p.getExteriorRing();
					lines.add(getNewPolyLine(shell));
				
					for (int j = 0; j < p.getNumInteriorRing(); j++) {
						LineString ring = p.getInteriorRingN(j);
						lines.add(getNewPolyLine(ring));
					}
					
				}
			}
		}

		
		this.area.setPolyLines(lines);
		
		
		
		this.area.setTransformationData(this.scale,this.envelope);
	}
	
	private PolyLine getNewPolyLine(LineString shell) {
		int length = shell.getNumPoints();
		int [] xs = new int[length];
		int [] ys = new int[length];
		for (int i = 0; i < length; i++) {
			Coordinate c = shell.getCoordinateN(i);
			int x = (int)(0.5+(this.scale*(c.x-this.envelope.getMinX())));
			int y = (int)(0.5+(this.scale*(c.y-this.envelope.getMinY())));
			xs[i] = x;
			ys[i] = y;
		}
			
		PolyLine p = new PolyLine();
		p.x = xs;
		p.y = ys;
		p.length = length;
		return p;
	}

	private void resize() {
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		double width = this.envelope.getMaxX() - this.envelope.getMinX();
		double height = this.envelope.getMaxY() - this.envelope.getMinY();
		
		this.scale = screenSize.getHeight() / height;
		if (width * scale > screenSize.width) {
			scale = screenSize.width / width;
		}
		
		this.f.setSize((int)(width*scale), (int)(height * scale));
		
	}

	public void update(Collection<Particle> collection) {
		for (Particle p : collection) {
			this.area.updateAgent(p);
		}
		long current = System.currentTimeMillis();
		long diff = current - this.lastUpdate;
		long wait = (long) (Config.TIME_STEP_SIZE * 1000) -diff;
		if (wait > 0) {
			try {
				Thread.sleep(wait);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.lastUpdate = System.currentTimeMillis();
		this.area.repaint();
		
	}

	private static class DrawArea extends JPanel {
		
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private Map<Particle,Agent> agents = new LinkedHashMap<Particle, Agent>();
		private List<PolyLine> env = new ArrayList<PolyLine>();
		private Envelope envelope;
		private double scale;
		private List<PolyLine> links = new ArrayList<PolyLine>();
		private List<Node> nodes = new ArrayList<Node>();

		public DrawArea(){
			 setBorder(BorderFactory.createLineBorder(Color.black));
		}

		public void setNodes(List<Node> nodes) {
			this.nodes  = nodes;
			
		}

		public void setPolyLines(List<PolyLine> lines) {
			this.env = lines;
			
		}
		
		public void setLinks(List<PolyLine> lines) {
			this.links = lines;
		}

		public void setTransformationData(double scale, Envelope envelope) {
			this.scale = scale;
			this.envelope = envelope;
			
			
		}

		@Override
		public void paint(Graphics arg0) {
			
			super.paint(arg0);
			
			
			for (Agent agent : this.agents.values()) {
				agent.paintAgent(arg0);
			}
			
			for (PolyLine p : this.env) {
				arg0.setColor(Color.black);
				arg0.drawPolyline(p.x, p.y, p.length);
			}
			
			int i = 0;
			for (PolyLine p : this.links) {
				arg0.setColor(Color.red);
				arg0.drawPolyline(p.x, p.y, p.length);
				arg0.drawString(Integer.toString(i++), (p.x[0]/2+p.x[1])/2+10,(p.y[0]/2+p.y[1])/2+10);
			}
			
			for (Node n : this.nodes) {
				arg0.setColor(Color.gray);
		    	arg0.fillOval(n.x-10, n.y-10, 20, 20);
		    	arg0.drawString(n.coordinate, n.x+15, n.y+15);
			}
		}
		
		public void updateAgent(Particle p) {
			Agent a = this.agents.get(p);
			if (a == null) {
				if (p instanceof BallParticle) {
					a = new Agent(Color.CYAN);
					a.setRadius((int)(0.5+(p.getDiameter()*this.scale)));
				}else {
					a = new Agent(new Color(MatsimRandom.getRandom().nextFloat(),MatsimRandom.getRandom().nextFloat(),MatsimRandom.getRandom().nextFloat()));
					a.setRadius((int)(0.5+(p.getDiameter()/2*this.scale)));
				}
				this.agents.put(p, a);
			}
			int x = (int) (0.5+(p.getPosition().x - this.envelope.getMinX()) * this.scale);
			int y = (int) (0.5+(p.getPosition().y - this.envelope.getMinY()) * this.scale);
			a.setLocation(x,y);
			double norm = 2 *Math.sqrt(Math.pow(p.getVx(), 2)+ Math.pow(p.getVy(), 2));
			if (p instanceof AgentParticle) {
				a.setDirection((int)(p.getVx()*this.scale/norm+0.5),(int) (p.getVy()*this.scale/norm+0.5));
			}
		}
	}
	

	public void shutdown() {
		this.f.dispose();
		
	}
	
	private static class Node {
		int x;
		int y;
		String coordinate;
	}
	
	private static class PolyLine {
		int [] x;
		int [] y;
		int length;
	}



}
